/*
 * This file is part of Space Invaders: Rehash
 * Copyright (C)2007 Vicent Marti (tanoku@gmail.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <memory.h>
#include <stdio.h>
#include <vector>

#include "SDL.h"
#include "SDL_ttf.h"
#include "SDL_opengl.h"

#include <GL/gl.h>
#include <GL/glu.h>
//#include <GL/glx.h>

#include "main.h"
#include "font_handler.h"
#include "gfx.h"

#define VIEW_DISTANCE -2.0f
extern SDL_Surface	*g_Screen;

FontHandler *g_Fonts;
bool g_2D = false;
float g_ScreenRatio;

std::string modelfile[MODEL_COUNT] = {
	"si_canon.obj",
	"si_missile.obj" };

std::string texfiles[TEXTURE_COUNT] = {
	"e1a.bmp", //0
	"e1b.bmp", //1
	"e2a.bmp", //2
	"e2b.bmp", //3
	"bg.bmp", //4
	"si_canon_mesh.bmp", //5
	"expl.bmp", //6
 	"smoke2.bmp", //7
	"mainmenu_bg.bmp", //8
	"mainmenu_bg_s1.bmp", //9
	"mainmenu_bg_s3.bmp", //10
	"mainmenu_bg_s4.bmp", //11
	"mainmenu_bg_s2.bmp", // 12
	"mainmenu_bg_text.bmp", //13
	"gamerules.bmp", // 14
	"conc.bmp", // 15
	"logo.bmp", //16
	"si_missile_mesh.bmp" }; //17
	
bool texfiles_alpha[TEXTURE_COUNT] = { 
	true, true, true, true, 
	false, false, false, false, 
	false, false, false, false, 
	false, false, false, false };
	
float UVINV[2] = { 1.0f, 0.0f };

GLuint g_Textures[TEXTURE_COUNT];
GLuint g_Models[MODEL_COUNT];

void glInit()
{
	glShadeModel( GL_SMOOTH );
	glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
	glClearDepth( 1.0f );
	glEnable( GL_DEPTH_TEST );
	glEnable(GL_TEXTURE_2D);
	glDepthFunc( GL_LEQUAL );
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	glAlphaBlend();
	
	glLoadTextures();
	glLoadModels();
}

void glResizeWindow( int width, int height )
{
	if ( !height ) height = 1;

	g_ScreenRatio = ( GLfloat )width / ( GLfloat )height;

	glViewport( 0, 0, ( GLsizei )width, ( GLsizei )height );

	
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective( 45.0f, g_ScreenRatio, 0.1f, 100.0f );
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity(); 
}

// HACKETY-HACK: glUnproject is slow as fuck, we are tryint go guess
// the size of the view frustrum based on the fov.
void glScreenToWorld( int x, int y, float &world_x, float &world_y, float &world_z )
{
	float h = tan(45.0f/2.0f)*(-VIEW_DISTANCE-0.5f);
	float w = h * g_ScreenRatio;
	
	world_z = VIEW_DISTANCE;
	
	world_x = x - (float)SCREEN_W/2;
	world_x /= (float)SCREEN_W/2;
	world_x *= w;
	
	world_y = (float)SCREEN_H/2-y;
	world_y /= (float)SCREEN_H/2;
	world_y *= h;
}

// jeez, is this fast enough??
// maybe I should post in gamedev or something, doesnt look like the best approach
void glEnable2D()
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho( 0, SCREEN_W, SCREEN_H, 0, -1, 1 );
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
}

void glDisable2D()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();   
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void glDrawRect(int x, int y, int width, int height, float r, float g, float b, float alpha )
{
	glLoadIdentity();
	glTranslatef( (GLfloat)x, (GLfloat)y, 0.0f );
	glBegin( GL_QUADS );
		glColor4f( r, g, b, alpha );
		glVertex3f( 0.0f,			0.0f,			0.0f );
		glVertex3f( (GLfloat)width,	0.0f,			0.0f );
		glVertex3f( (GLfloat)width,	(GLfloat)height,0.0f );
		glVertex3f( 0.0f,			(GLfloat)height,0.0f );
	glEnd();
}

void glDrawOutlinedRect(int x, int y, int width, int height, int outline_w, float r, float g, float b, float alpha )
{
	glDrawRect(x, y, outline_w, height, r, g, b, alpha);
	glDrawRect(x, y, width, outline_w, r, g, b, alpha);
	glDrawRect(x+width-outline_w, y, outline_w, height, r, g, b, alpha);
	glDrawRect(x, y+height-outline_w, width, outline_w, r, g, b, alpha);
}

void glDrawTexuredRect(int x, int y, int width, int height, GLuint texture, float r, float g, float b, bool inverted)
{
	glLoadIdentity();
	glTranslatef( (GLfloat)x, (GLfloat)y, 0 );
	
	glColor4f( r, g, b, 1.0 );
	glBindTexture(GL_TEXTURE_2D, texture);
	
	glBegin( GL_QUADS );
		glTexCoord2f( 0.0f, (float)(!inverted) ); glVertex3f( 0.0f,			0.0f,			0.0f );
		glTexCoord2f( 1.0f, (float)(!inverted) ); glVertex3f( (GLfloat)width, 0.0f,			0.0f );
		glTexCoord2f( 1.0f, (float)(inverted) ); glVertex3f( (GLfloat)width,	(GLfloat)height,0.0f );
		glTexCoord2f( 0.0f, (float)(inverted) ); glVertex3f( 0.0f,			(GLfloat)height,0.0f );
	glEnd();
	
	glBindTexture (GL_TEXTURE_2D, 0);
}

// TODO: inline this?
void glPrint( int font, int x, int y, const char *fmt )
{
	g_Fonts->get(font)->drawText(fmt, x, y);
}

void glPrint_br( int font, int x, int y, const char *fmt )
{
	GLFont *glf = g_Fonts->get(font);
	if ( !glf ) return;
	
	using namespace std;
	size_t p0 = 0, p1 = string::npos;
	string str = fmt;
	
	while(p0 != string::npos)
	{
		p1 = str.find_first_of("\n", p0);
		if(p1 != p0)
		{
			glf->drawText(str.substr(p0, p1 - p0).c_str(), x, y);
			y += glf->getHeight();
		}
		p0 = str.find_first_not_of("\n", p1);
	}
}

void glPrintf( int font, int x, int y, const char *fmt, ... )
{
	char text[256];
	va_list ap;
	
	va_start( ap, fmt );
		vsprintf( text, fmt, ap );
	va_end( ap );

	glPrint(font, x, y, text);
}

void glPrintf_br( int font, int x, int y, const char *fmt, ... )
{
	char text[256];
	va_list ap;
	
	va_start( ap, fmt );
		vsprintf( text, fmt, ap );
	va_end( ap );

	glPrint_br(font, x, y, text);
}

void glDrawCube( float x, float y, float z, float size, 
				 float r, float g, float b, float rotation,
				 float rx, float ry, float rz )
{
	glLoadIdentity();
	glTranslatef( x, y, z );
	glRotatef( rotation, rx, ry, rz );
	glColor3f( r, g, b );
	
	glBegin( GL_QUADS );
		glVertex3f(  size,  size, -size ); 
		glVertex3f( -size,  size, -size ); 
		glVertex3f( -size,  size,  size ); 
		glVertex3f(  size,  size,  size );
		
		glVertex3f(  size, -size,  size );
		glVertex3f( -size, -size,  size );
		glVertex3f( -size, -size, -size );
		glVertex3f(  size, -size, -size );
		
		glVertex3f(  size,  size,  size ); 
		glVertex3f( -size,  size,  size ); 
		glVertex3f( -size, -size,  size ); 
		glVertex3f(  size, -size,  size );
		
		glVertex3f(  size, -size, -size ); 
		glVertex3f( -size, -size, -size ); 
		glVertex3f( -size,  size, -size ); 
		glVertex3f(  size,  size, -size ); 
		
		glVertex3f( -size,  size,  size );
		glVertex3f( -size,  size, -size ); 
		glVertex3f( -size, -size, -size ); 
		glVertex3f( -size, -size,  size );
		
		glVertex3f(  size,  size, -size ); 
		glVertex3f(  size,  size,  size ); 
		glVertex3f(  size, -size,  size ); 
		glVertex3f(  size, -size, -size ); 
	glEnd();                       
}

GLuint glLoadTextureCK(const char *texture_file, int ckr, int ckg, int ckb)
{
	GLuint texture;
//	int w, h;
	SDL_Surface *image, *surface;
	SDL_Rect area;
	Uint32 colorkey;

	surface = SDL_LoadBMP( texture_file );

	image = SDL_CreateRGBSurface( SDL_SWSURFACE, powerOfTwo(surface->w), powerOfTwo(surface->h), 32,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN // OpenGL RGBA masks 
	0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
#else
	0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF
#endif
	);

	if (image == NULL)
		return 0;


	colorkey = SDL_MapRGBA(image->format, ckr, ckg, ckb, 0);
	SDL_FillRect(image, NULL, colorkey);

	colorkey = SDL_MapRGBA(surface->format, ckr, ckg, ckb, 0);
	SDL_SetColorKey(surface, SDL_SRCCOLORKEY, colorkey);

	// Copy the surface into the GL texture image 
	area.x = 0;
	area.y = 0;
	area.w = surface->w;
	area.h = surface->h;
	SDL_BlitSurface(surface, &area, image, &area);

	// Create an OpenGL texture for the image 

	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->w, image->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);

	SDL_FreeSurface(image); // No longer needed 

	return texture;
}

GLuint glLoadTexture(const char *texture_file)
{
	SDL_Surface *texture = SDL_LoadBMP( texture_file );
	GLuint texId;

	if ( !texture )
		return 0;

	int width = texture->w;
	int height = texture->h;
	int bpp = texture->format->BytesPerPixel;
	int imageSize = (width * height * bpp) - bpp;

	GLubyte *data = (GLubyte*) malloc(imageSize);
	
	GLubyte* temp = (GLubyte*) malloc(imageSize);
	memcpy(temp, texture->pixels, imageSize);

	for (int j = 0; j < imageSize; j+=bpp)
	{
		data[j] = temp[imageSize - j];
		data[j+1] = temp[imageSize - j+1];
		data[j+2] = temp[imageSize - j+2];
	}

	memcpy(temp, data, imageSize);

	int w = 0, x = 0, y = 0;
	for (int j = 0; j < imageSize; j+=bpp)
	{
		w = ((y + 1) * width*3) - (x*3) - 3;

		data[j] = temp[w];
		data[j+1] = temp[w+1];
		data[j+2] = temp[w+2];

		x++;
		if (x == width)
		{
			x = 0;
			y++;
		}
	}

	free(temp);

	glGenTextures( 1, &texId );
	glBindTexture( GL_TEXTURE_2D, texId );
	glTexImage2D( GL_TEXTURE_2D, 0, 3, texture->w, texture->h, 0, GL_BGR, GL_UNSIGNED_BYTE, data );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

	SDL_FreeSurface( texture );

	return texId;
}

void glLoadTextures()
{	
	for ( int i=0; i<TEXTURE_COUNT; ++i )
	{
		std::string file = getDataPath();
		file += texfiles[i];
		if ( texfiles_alpha[i] )
		{
			g_Textures[i] = glLoadTextureCK(file.c_str(), 0, 0, 0);
		}
		else
		{
			g_Textures[i] = glLoadTexture( file.c_str() );
		}
	}
}

GLuint glGetEmptyTex()							
{
	GLuint txtnumber;						
	unsigned int* data;						

	data = (unsigned int*)new GLuint[((MENU_TEX_SIZE * MENU_TEX_SIZE)* 4 * sizeof(unsigned int))];

	glGenTextures(1, &txtnumber);					
	glBindTexture(GL_TEXTURE_2D, txtnumber);			
	glTexImage2D(GL_TEXTURE_2D, 0, 4, MENU_TEX_SIZE, MENU_TEX_SIZE, 0,
	GL_RGBA, GL_UNSIGNED_BYTE, data);			
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	
	delete [] data;							
	
	return txtnumber;
}


void glDrawCubeFace( float x, float y, float z, float size, float r, float g, float b, GLuint texture )
{
	glLoadIdentity();
	glTranslatef( x, y, z );
	glColor3f( r, g, b );
	glBindTexture(GL_TEXTURE_2D, texture);
	
	glBegin( GL_QUADS );
		glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -size, -size, size );
		glTexCoord2f( 1.0f, 0.0f ); glVertex3f(  size, -size, size );
		glTexCoord2f( 1.0f, 1.0f ); glVertex3f(  size,  size, size );
		glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -size,  size, size );
	glEnd(); 
	
	glBindTexture (GL_TEXTURE_2D, 0);
}

void glDrawModel( GLuint model, float x, float y, float z, GLuint texture,
				  float r, float g, float b, float scale, float rotation,
				  float rx, float ry, float rz )
{	
	glLoadIdentity();
	glTranslatef( x, y, z );
	glRotatef( rotation, rx, ry, rz );
	glScalef( scale, scale, scale );
	glColor3f( r, g, b );
	glBindTexture(GL_TEXTURE_2D, texture);
	glCallList( model );
	glBindTexture (GL_TEXTURE_2D, 0);
}

void glLoadModels()
{
	for ( int i=0; i<MODEL_COUNT; ++i )
	{
		char* memory = NULL;
		std::string file = getDataPath();
		file += modelfile[i];
		size_t bytes = glLoadOBJ(file.c_str(), &memory);
		
		if ( !bytes )
			continue;

		ObjModel *model = glParseOBJ(memory, bytes);
		ObjTriangle *tri = NULL;

		g_Models[i] = glGenLists(1);
		glNewList( g_Models[i], GL_COMPILE );
			glBegin( GL_TRIANGLES );
				for ( int j = 0; j < model->nTriangle; j++ )
				{
					tri = &model->TriangleArray[j];                                
					for ( int i=0; i<3; ++i )
					{
						glNormal3f(model->NormalArray[tri->Normal[i]-1].x,
								   model->NormalArray[tri->Normal[i]-1].y,
								   model->NormalArray[tri->Normal[i]-1].z );
			
						glTexCoord2f(model->TexCoordArray[tri->TexCoord[i]-1].u,
									 model->TexCoordArray[tri->TexCoord[i]-1].v);
			
						glVertex3f(model->VertexArray[tri->Vertex[i]-1].x,
								   model->VertexArray[tri->Vertex[i]-1].y,
								   model->VertexArray[tri->Vertex[i]-1].z );
					}
				}
			glEnd();
		glEndList();

		printf("Loaded %s with %d faces.\n", modelfile[i].c_str(), model->nTriangle);
		free(memory);
		delete model;
	}
	
}

ObjModel* glParseOBJ(char* memory, size_t size)
{
	char* p = NULL, * e = NULL;
	ObjModel* ret = (ObjModel*) calloc(1, sizeof(ObjModel));
	memset(ret, 0, sizeof(ObjModel));

	p = memory;
	e = memory + size;
	
	while (p != e)
	{
		if (memcmp(p, "vn", 2) == 0) ret->nNormal++;
		else if (memcmp(p, "vt", 2) == 0) ret->nTexCoord++;
		else if (memcmp(p, "v",  1) == 0) ret->nVertex++;
		else if (memcmp(p, "f",  1) == 0) ret->nTriangle++;

		while (*p++ != (char) 0x0A);
	}

	ret->VertexArray   = (ObjVertex*)   malloc(sizeof(ObjVertex) * ret->nVertex);
	ret->NormalArray   = (ObjNormal*)   malloc(sizeof(ObjNormal) * ret->nNormal);
	ret->TexCoordArray = (ObjTexCoord*) malloc(sizeof(ObjTexCoord) * ret->nTexCoord);
	ret->TriangleArray = (ObjTriangle*) malloc(sizeof(ObjTriangle) * ret->nTriangle);

	p = memory;
	
	int nV = 0, nN = 0, nT = 0, nF = 0;
	
	while (p != e)
	{
		if (memcmp(p, "vn", 2) == 0)
		{
			sscanf(p, "vn %f %f %f", &ret->NormalArray[nN].x,
			&ret->NormalArray[nN].y,
			&ret->NormalArray[nN].z);
			nN++;
		}
		else if (memcmp(p, "vt", 2) == 0)
		{
			sscanf(p, "vt %f %f", &ret->TexCoordArray[nT].u,
			&ret->TexCoordArray[nT].v);
			nT++;
		}
		else if (memcmp(p, "v", 1) == 0)
		{
			sscanf(p, "v %f %f %f", &ret->VertexArray[nV].x,
			&ret->VertexArray[nV].y,
			&ret->VertexArray[nV].z);
			nV++;
		}
		else if (memcmp(p, "f", 1) == 0)
		{
			sscanf(p, "f %d/%d/%d %d/%d/%d %d/%d/%d", 
			&ret->TriangleArray[nF].Vertex[0],
			&ret->TriangleArray[nF].TexCoord[0],
			&ret->TriangleArray[nF].Normal[0],
			&ret->TriangleArray[nF].Vertex[1],
			&ret->TriangleArray[nF].TexCoord[1],
			&ret->TriangleArray[nF].Normal[1],
			&ret->TriangleArray[nF].Vertex[2],
			&ret->TriangleArray[nF].TexCoord[2],
			&ret->TriangleArray[nF].Normal[2]);
			nF++;
		}

		while (*p++ != (char) 0x0A);
	}
	
	return ret;
}

size_t glLoadOBJ(const char *szFileName, char** memory)
{
	size_t bytes = 0;
	FILE* file = fopen(szFileName, "rt");

	if (file != NULL)
	{
		fseek(file, 0, SEEK_END);
		size_t end = ftell(file);
		fseek(file, 0, SEEK_SET);
		
		*memory = (char*) malloc(end);
		bytes = fread(*memory, sizeof(char), end, file);

		fclose(file);
	}

	return bytes;
}
